﻿using Avalonia;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
using SharpAdbClient;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Net;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;

namespace LayoutViewer.Models.Helpers
{
    public class AdbHelper : BaseHelper
    {
        internal override Action<Bitmap, MoveCategory> OnSucceed { get; set; }
        internal override Action<string> OnFailed { get; set; }

        private readonly Dictionary<string, DeviceData> devices = new();

        private readonly AdbClient client = new();
        private readonly DnsEndPoint endPoint;

        private readonly string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory.Replace("\\", "/"), "assets", "adb");

        internal AdbHelper(string ip, int port) : this()
        {
            this.endPoint = new DnsEndPoint(ip, port);
            this.client.Connect(this.endPoint);
        }

        internal AdbHelper()
        {
            if (!AdbServer.Instance.GetStatus().IsRunning)
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    AdbServer.Instance.StartServer(Path.Combine(this.path, "adb.exe"), false);
                }
                else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
                {
                    AdbServer.Instance.StartServer(Path.Combine(this.path, "adb"), false);
                }
            }
        }

        internal override void Close()
        {
            if (this.endPoint is not null)
            {
                this.client.Disconnect(this.endPoint);
            }
        }

        internal override async Task<List<KeyValuePair<int, string>>> GetList()
        {
            this.devices.Clear();
            var result = new List<KeyValuePair<int, string>>();
            return await Task.Run(() =>
            {
                var devices = this.client.GetDevices();
                for (var i = 0; i < devices.Count; i++)
                {
                    var device = devices[i];
                    if (device.State == DeviceState.Online)
                    {
                        this.devices.Add(device.Serial, device);
                        result.Add(new KeyValuePair<int, string>(i, device.Serial));
                    }
                }

                if (this.devices.Count == 0)
                {
                    return new List<KeyValuePair<int, string>>
                    {
                        new KeyValuePair<int, string>(0, "null")
                    };
                }
                return result;
            });
        }

        internal override async void LayoutDump(string name)
        {
            try
            {
                var bitmap = await this.ScreenShot(name);
                var layout = new MoveCategory { ClassName = "RootLayout" };
                var document = await this.DumpScreen(this.GetDevice(name));
                if (bitmap is null || document is null) return;

                ParseElements(document.Elements(), ref layout);
                this.OnSucceed?.Invoke(bitmap, layout);
            }
            catch (Exception ex)
            {
                this.OnFailed?.Invoke($"获取布局失败：{ex.Message}");
            }
        }


        private DeviceData GetDevice(string name)
        {
            if (this.devices.ContainsKey(name))
            {
                return this.devices[name];
            }
            throw new Exception("未找到对应设备!");
        }

        private async Task<Bitmap> ScreenShot(string name)
        {
            return await Task.Run(async () =>
            {
                var framebuffer = this.client.CreateRefreshableFramebuffer(this.GetDevice(name));
                await framebuffer.RefreshAsync(CancellationToken.None);
                var data = framebuffer.Data;
                var width = (int)framebuffer.Header.Width;
                var height = (int)framebuffer.Header.Height;
                var sKBitmap = new SKBitmap(new SKImageInfo(width, height));
                var pxFormat = data.Length / height / width;
                var dataStep = 0;

                unsafe
                {
                    var intPtr = (byte*)sKBitmap.GetPixels();
                    for (var y = 0; y < height; y++)
                    {
                        var intPtrStep = y * width * 4;

                        for (var x = 0; x < width; x++)
                        {
                            intPtr[intPtrStep] = data[dataStep + 2];
                            intPtr[intPtrStep + 1] = data[dataStep + 1];
                            intPtr[intPtrStep + 2] = data[dataStep];

                            if (pxFormat == 3)
                            {
                                intPtr[intPtrStep + 3] = 255;
                                dataStep += 3;
                            }
                            else
                            {
                                intPtr[intPtrStep + 3] = data[dataStep + 3];
                                dataStep += 4;
                            }
                            intPtrStep += 4;
                        }
                    }
                }
                var bitmap = new Bitmap(PixelFormat.Bgra8888, AlphaFormat.Opaque, sKBitmap.GetPixels(), new PixelSize(width, height), new Vector(96, 96), sKBitmap.RowBytes);
                sKBitmap.Dispose();
                return bitmap;
            });
        }

        public static void ParseElements(IEnumerable<XElement> elements, ref MoveCategory root)
        {
            foreach (var element in elements)
            {
                var node = ParseElement(element);
                if (element.HasElements)
                {
                    ParseElements(element.Elements(), ref node);
                }
                root.Children ??= new ObservableCollection<MoveCategory>();
                root.Children.Add(node);
            }

        }

        private static MoveCategory ParseElement(XElement element)
        {
            var result = new MoveCategory();
            foreach (var attr in element.Attributes())
            {
                switch (attr.Name.ToString())
                {
                    case "index":
                        result.IndexInParent = int.Parse(attr.Value); break;
                    case "text":
                        result.Text = attr.Value; break;
                    case "resource-id":
                        result.Id = attr.Value; break;
                    case "class":
                        result.ClassName = attr.Value; break;
                    case "package":
                        result.PackageName = attr.Value; break;
                    case "content-desc":
                        result.Desc = attr.Value; break;
                    case "checkable":
                        result.Checkable = bool.Parse(attr.Value); break;
                    case "checked":
                        result.Checked = bool.Parse(attr.Value); break;
                    case "clickable":
                        result.Clickable = bool.Parse(attr.Value); break;
                    case "enabled":
                        result.Enabled = bool.Parse(attr.Value); break;
                    case "focused":
                        result.Focused = bool.Parse(attr.Value); break;
                    case "scrollable":
                        result.Scrollable = bool.Parse(attr.Value); break;
                    case "long-clickable":
                    case "long-Clickable":
                        result.LongClickable = bool.Parse(attr.Value); break;
                    case "selected":
                        result.Selected = bool.Parse(attr.Value); break;
                    case "bounds":
                        result.Bounds = Rect.Parse(attr.Value); break;

                    default:
                        break;
                }
            }
            return result;
        }

        private async Task<XDocument> DumpScreen(DeviceData device)
        {
            var receiver = new ConsoleOutputReceiver();
            await this.client.ExecuteRemoteCommandAsync("uiautomator dump /dev/tty", device, receiver, Encoding.UTF8, CancellationToken.None);
            var output = receiver.ToString();
            if (!string.IsNullOrEmpty(output))
            {
                var startNum = output.IndexOf("<node");
                var len = output.LastIndexOf("</node>") + 7 - startNum;
                var xml = output.Substring(startNum, len);

                return XDocument.Parse(xml);
            }
            return null;
        }


    }
}
